#pragma once
#include "Diagnostic.hpp"
#include "Symbol/TypeSymbol.hpp"
#include "Syntax/Kind.hpp"
#include "Syntax/Object/Object.hpp"

#include <cstddef>
#include <format>
#include <memory>
#include <vector>

class DiagnosticBag;

typedef std::shared_ptr<DiagnosticBag> SharedDiagnosticBag;

class DiagnosticBag
{
public:
    DiagnosticBag() : _diagnostics() {}

public:
    void AddRange(SharedDiagnosticBag diagnostics)
    {
        for (auto var : diagnostics->_diagnostics)
        {
            this->_diagnostics.push_back(var);
        }
    }
    void InsertRange(SharedDiagnosticBag diagnostics)
    {
        auto it = this->_diagnostics.begin();

        for (auto var : diagnostics->_diagnostics)
        {
            it = this->_diagnostics.insert(it, var);
        }
    }
    void ReportInvalidNumber(TextSpan span, std::string text, SharedTypeSymbol type)
    {
        auto message = std::format("The number {} isn't valid {}.", text, type->ToString());
        Report(span, message);
    }
    void ReportUnterminatedString(TextSpan span)
    {
        auto message = std::format("Unterminated string literal.");
        Report(span, message);
    }

    void ReportBadCharacter(size_t position, char character)
    {
        auto span    = TextSpan(position, 1);
        auto message = std::format("Bad character input: '{}'", character);
        Report(span, message);
    }
    void ReportUnexpectedKindError(TextSpan span, SyntaxKind actualKind, std::string kindErrorText)
    {
        auto message = std::format("Unexpected token <{}>:{}", SyntaxKindName::GetTokenName(actualKind), kindErrorText);
        Report(span, message);
    }
    void ReportUnexpectedToken(TextSpan span, SyntaxKind actualKind, SyntaxKind expectedKind)
    {
        auto message =
            std::format("Unexpected token <{}>, expected <{}>.", SyntaxKindName::GetTokenName(actualKind), SyntaxKindName::GetTokenName(expectedKind));
        Report(span, message);
    }
    void ReportUndefinedUnaryOperator(TextSpan span, std::string operatorText, SharedTypeSymbol operandType)
    {
        auto message = std::format("Unary operator '{}' is not defined for type '{}'.", operatorText, operandType->ToString());
        Report(span, message);
    }
    void ReportUndefinedBinaryOperator(TextSpan span, std::string operatorText, SharedTypeSymbol leftType, SharedTypeSymbol rightType)
    {
        auto message = std::format("Binary operator '{}' is not defined for types '{}' and '{}'.", operatorText, leftType->ToString(), rightType->ToString());
        Report(span, message);
    }
    void ReportUndefinedName(TextSpan span, std::string name)
    {
        auto message = std::format("Variable '{}' doesn't exist.", name);
        Report(span, message);
    }
    void ReportCannotConvert(TextSpan span, SharedTypeSymbol fromType, SharedTypeSymbol toType)
    {
        auto message = std::format("Cannot convert type '{}' to '{}'.", fromType->ToString(), toType->ToString());
        Report(span, message);
    }
    void ReportVariableAlreadyDeclared(TextSpan span, std::string name)
    {
        auto message = std::format("Variable '{}' is already declared.", name);
        Report(span, message);
    }
    void ReportCannotAssign(TextSpan span, std::string name)
    {
        auto message = std::format("Variable '{}' is read-only and cannot be assigned to.", name);
        Report(span, message);
    }

private:
    void Report(TextSpan span, std::string message)
    {
        auto diagnostic = std::make_shared<Diagnostic>(span, message);
        _diagnostics.push_back(diagnostic);
    }

public:
    std::vector<SharedDiagnostic> Diagnostics() { return this->_diagnostics; }

private:
    std::vector<SharedDiagnostic> _diagnostics;
};
